Gunslinger Joe's Gold Stash
Description
Author: cutz
Category: Reversing
Silly Gunslinger Joe has learned from his mistakes with his private terminal and now tries to remember passwords. But he’s gotten more paranoid and chose to develope an additional method: protect all his private stuff with a secure locking mechanism that no one would be able to figure out! He’s so confident with this new method that he even started using it to protect all his precious gold. So… we better steal all of it!
SSH: joes_gold@wildwildweb.fluxfingers.net
PORT: 1415
PASSWORD: 1gs67uendsx71xmma8
Solution
Author: Sam Thomas / xorpse
After logging in, we see that there are two files in the home directory:
joes_gold@goldstash:~$ ls -l
total 20
-r-------- 1 gold gold 46 Oct 6 23:04 FLAG
-rwsr-sr-x 1 gold gold 13186 Oct 6 23:03 gold_stash
Notice the setuid and setgid bits on gold_stash
: when executed, gold_stash
will run as if started by the user gold
. Note also that FLAG
is only
readable by the user gold
.
After transferring gold_stash
to a local directory and executing it, we are
presented with a basic login prompt:
.
(_/-------------_______________________)
`| /~~~~~~~~~~\ |
; |--------(-||______________________|
; |--------(-| ____________|
; \__________/'
_/__ ___;
,~~ | __--~~ Gunslinger Joe's
' ~~| ( | Private Stash of Gold
' '~~ `____'
' '
' ` Password Protection activated!
' `
'--------`
Username:
Disassembling the main function, gives us the credential input routine:
.text:000000000040093E mov edi, offset format ; "Username: "
.text:0000000000400943 mov eax, 0
.text:0000000000400948 call _printf
.text:000000000040094D mov rax, cs:__bss_start
.text:0000000000400954 mov rdi, rax ; stream
.text:0000000000400957 call _fflush
.text:000000000040095C lea rax, [rbp+s]
.text:0000000000400963 mov edx, 0FFh ; nbytes
.text:0000000000400968 mov rsi, rax ; buf
.text:000000000040096B mov edi, 0 ; fd
.text:0000000000400970 call _read
.text:0000000000400975 mov edi, offset aPassword ; "Password: "
.text:000000000040097A mov eax, 0
.text:000000000040097F call _printf
.text:0000000000400984 mov rax, cs:__bss_start
.text:000000000040098B mov rdi, rax ; stream
.text:000000000040098E call _fflush
.text:0000000000400993 lea rax, [rbp+buf]
.text:000000000040099A mov edx, 0FFh ; nbytes
.text:000000000040099F mov rsi, rax ; buf
.text:00000000004009A2 mov edi, 0 ; fd
.text:00000000004009A7 call _read
Followed by an uninteresting section of code for removing trailing newlines (due to the use of read).
And finally, the authentication check using hardcoded credentials:
.text:0000000000400A0C lea rax, [rbp+s]
.text:0000000000400A13 mov esi, offset s2 ; "Joe"
.text:0000000000400A18 mov rdi, rax ; s1
.text:0000000000400A1B call _strcmp
.text:0000000000400A20 test eax, eax
.text:0000000000400A22 jnz short loc_400A9A ; To authentication failed
.text:0000000000400A24 lea rax, [rbp+buf]
.text:0000000000400A2B mov esi, offset aOmg_joe_is_so_ ; "omg_joe_is_so_rich"
.text:0000000000400A30 mov rdi, rax ; s1
.text:0000000000400A33 call _strcmp
.text:0000000000400A38 test eax, eax
.text:0000000000400A3A jnz short loc_400A9A ; To authentication failed
.text:0000000000400A3C mov edi, offset aAccessGranted ; "Access granted!"
So with the user/password combination (Joe/omg_joe_is_so_rich), we try to gain access locally:
Username: Joe
Password: omg_joe_is_so_rich
Access granted!
sh-4.3$
Success! However, applying the same method on the challenge server yields unexpected results:
Username: Joe
Password: omg_joe_is_so_rich
Authentication failed!
What could be wrong? If we debug with gdb on the challenge server we get the
expected result. However, gdb executes gold_stash
as the user joes_gold
rather than gold
. Ergo, one of strcmp
or read
must be behaving
differently when executed as the user gold
.
A look into the libc.so.6
binary on the system reveals nothing untoward;
another possibilty is that a system call has been hijacked, and an ls
of the
kernel modules directory seems to point in that direction (notice the joe
directory):
joes_gold@goldstash:~$ ls -l /lib/modules/3.13.0-36-generic/kernel/
total 40
drwxr-xr-x 3 root root 4096 Oct 6 21:28 arch
drwxr-xr-x 3 root root 4096 Oct 6 21:28 crypto
drwxr-xr-x 77 root root 4096 Oct 6 21:28 drivers
drwxr-xr-x 55 root root 4096 Oct 6 21:28 fs
drwxr-xr-x 2 root root 4096 Oct 6 23:08 joe
drwxr-xr-x 6 root root 4096 Oct 6 21:28 lib
drwxr-xr-x 2 root root 4096 Oct 6 21:29 mm
drwxr-xr-x 51 root root 4096 Oct 6 21:28 net
drwxr-xr-x 13 root root 4096 Oct 6 21:28 sound
drwxr-xr-x 4 root root 4096 Oct 6 21:28 ubuntu
…which contains a module joe.ko
. Such module should contain an init
function. In this case, joe_init
. In order to hijack a system call, the
system call table must be located:
.text:00000000000001E6 mov rax, 0FFFFFFFF81000000h
.text:00000000000001ED mov rbp, rsp
.text:00000000000001F0 jmp short loc_204
.text:00000000000001F0 ; ---------------------------------------------------------------------------
.text:00000000000001F2 align 8
.text:00000000000001F8
.text:00000000000001F8 loc_1F8: ; CODE XREF: joe_init+2Cj
.text:00000000000001F8 add rax, 8
.text:00000000000001FC cmp rax, 0FFFFFFFFA2000000h
.text:0000000000000202 jz short loc_268
.text:0000000000000204
.text:0000000000000204 loc_204: ; CODE XREF: joe_init+10j
.text:0000000000000204 cmp qword ptr [rax+18h], offset sys_close
.text:000000000000020C jnz short loc_1F8
.text:000000000000020E test rax, rax
.text:0000000000000211 mov cs:sct, rax
Once found, it’s stored within sct
. read
is the first system call, (i.e. at
offset 0 within sct
) and is replaced with a function called joe
:
.text:0000000000000231 mov rdx, cs:sct
.text:0000000000000238 mov rax, offset joe
.text:000000000000023F xchg rax, [rdx]
.text:0000000000000242 mov cs:o_read, rax
The address of the original read function is saved to o_read
.
The function joe
(from a high-level) works as:
- Read buffer using
o_read
; - Copy buffer from user to kernel using
copy_from_user
; - If uid == 1001: encode first 18 characters using:
char *pad = "123456789012445678";
char *input = ...
for (int i = 0; i < 18; i++)
input[i] = pad[i] ^ (input[i] - 4);
- If the original string compared equal to ‘omg_joe_is_so_rich’ or if the original string had the prefix ‘omg_joe_is_so_rich’ then the encoded value replaces it (via copy_to_user).
To get the desired string we need to encode ‘omg_joe_is_so_rich’:
char *pad = "123456789012445678";
char *input = "omg_joe_is_so_rich";
for (int i = 0; i < 18; i++) {
input[i] = (input[i] ^ pad[i]) + 4;
}
Which gives: bcXoc]VkTGrE_oKcXT as the encoded password.
The FLAG file can then be read:
Username: Joe
Password: bcXoc]VkTGrE_oKcXT
Access granted!
$ cat FLAG
flag{joe_thought_youd_never_find_that_module}